#include "shell.h"
#include "shell_ext.h"
#include "stdint.h"
#include "stdio.h"
#include "usart.h"
#include "signalgenerator.h"

extern uint8_t uartDataFlag;
extern char uartReDate[REC_LENGTH];

static SHELL_TypeDef *shellList[SHELL_MAX_NUMBER] = { NULL };//shell table

static void shellAdd(SHELL_TypeDef *shell);
static void shellDisplayItem(SHELL_TypeDef *shell, unsigned short index);


	
/***********************************************************************************************/
const SHELL_CommandTypeDef shellDefaultCommandList[] =
{
	SHELL_CMD_ITEM_EX(Help, shellHelp, command help, help[command]--show help info of command),
	SHELL_CMD_ITEM(Clear, shellClear, clear command line),
	SHELL_CMD_ITEM(ChipID, STM32_ID, STM32Fxxx ID),
	SHELL_CMD_ITEM(Sine, StartDACSine, SineWave),
};
/***********************************************************************************************/

void shellInit(SHELL_TypeDef *shell)
{
	shellDisplay(shell, "\r\n\r\n");
	shellDisplay(shell, "+=========================================================+\r\n");
	shellDisplay(shell, "|                (C) COPYRIGHT 2019 Cairne                |\r\n");
	shellDisplay(shell, "|                   UShell v"SHELL_VERSION"                         |\r\n");
	shellDisplay(shell, "|               Build: "__DATE__" "__TIME__"               |\r\n");
	shellDisplay(shell, "+=========================================================+\r\n");
	shell->length = 0;
	shell->cursor = 0;
	shell->historyCount = 0;
	shell->historyFlag = 0;
	shell->historyOffset = 0;
	shell->status = SHELL_IN_NORMAL;
	shell->command = SHELL_DEFAULT_COMMAND;
	shell->isActive = 0;
	shellAdd(shell);
	shellDisplay(shell, shell->command);
    
	shell->commandBase = (SHELL_CommandTypeDef *)shellDefaultCommandList;
	shell->commandNumber = sizeof(shellDefaultCommandList) / sizeof(SHELL_CommandTypeDef);
	

}


void shellSetCommandList(SHELL_TypeDef *shell, SHELL_CommandTypeDef *base, unsigned short size)
{
	shell->commandBase = base;
	shell->commandNumber = size;
}

static void shellAdd(SHELL_TypeDef *shell)
{
	for (short i = 0; i < SHELL_MAX_NUMBER; i++)
	{
		if (shellList[i] == NULL)
		{
			shellList[i] = shell;
			return;
		}
	}
}

SHELL_TypeDef *shellGetCurrent(void)
{
	for (short i = 0; i < SHELL_MAX_NUMBER; i++)
	{
		if (shellList[i] != NULL && shellList[i]->isActive == 1)
		{
			return shellList[i];
		}
	}
	return NULL;
}

unsigned short shellDisplay(SHELL_TypeDef *shell, const char *string)
{
	unsigned short count = 0;
	if (shell->write == NULL)
	{
		return 0;
	}
	while (*string)
	{
		shell->write(*string++);
		count++;
	}
	return count;
}

static void shellDisplayByte(SHELL_TypeDef *shell, char data)
{
	if (shell->write == NULL)
	{
		return;
	}
	shell->write(data);
}

static void shellDisplayReturn(SHELL_TypeDef *shell, int value)
{
	unsigned char str[11] = "0000000000";
	unsigned int v = value;
	char i = 10;
	unsigned char tmp;

	shellDisplay(shell, "Return: ");
	if (value < 0)
	{
		shellDisplay(shell, "-");
		v = -v;
	}
	while (v)
	{
		str[--i] =v % 10 + 48;
		v /= 10;
	}
	shellDisplay(shell, str + i - (value == 0));
	v = value;
	if (value < 0)
	{
		v = (unsigned int)value;
	}
	i = 8;
	str[8] = 0;
	while (v)
	{
		tmp = v & 0x0000000F;
		str[--i] = (tmp > 9) ? (tmp + 87) : (tmp + 48);
		v >>= 4;
	}
	shellDisplay(shell, ", 0x");
	shellDisplay(shell, str);
	shellDisplay(shell, "\r\n");
}

static unsigned short shellStringCopy(char *dest, char* src)
{
	unsigned short count = 0;
	while (*(src + count))
	{
		*(dest + count) = *(src + count);
		count++;
	}
	*(dest + count) = 0;
	return count;
}

static unsigned short shellStringCompare(char* dest, char *src)
{
	unsigned short match = 0;
	unsigned short i = 0;

	while (*(dest + i) && *(src + i))
	{
		if (*(dest + i) != *(src + i))
		{
			break;
		}
		match++;
		i++;
	}
	return match;
}

static void shellDelete(SHELL_TypeDef *shell, unsigned short length)
{
	while (length--)
	{
		shellDisplay(shell, "\b \b");
	}
}

static void shellClearLine(SHELL_TypeDef *shell)
{
	for (short i = shell->length - shell->cursor; i > 0; i--)
	{
		shellDisplayByte(shell, ' ');
	}
	shellDelete(shell, shell->length);
}

static void shellHistoryAdd(SHELL_TypeDef *shell)
{
	if (strcmp(shell->history[shell->historyFlag - 1], shell->buffer) == 0)
	{
		return;
	}
	if (shellStringCopy(shell->history[shell->historyFlag], shell->buffer) != 0)
	{
		shell->historyFlag++;
	}
	if (++shell->historyCount > SHELL_HISTORY_MAX_NUMBER)
	{
		shell->historyCount = SHELL_HISTORY_MAX_NUMBER;
	}
	if (shell->historyFlag >= SHELL_HISTORY_MAX_NUMBER)
	{
		shell->historyFlag = 0;
	}
	shell->historyOffset = 0;
}

static void shellHistory(SHELL_TypeDef *shell, unsigned char dir)
{
	if (dir == 0)
	{
		if (shell->historyOffset--
		    <= -((shell->historyCount > shell->historyFlag)
		    ? shell->historyCount : shell->historyFlag))
		{
			shell->historyOffset = -((shell->historyCount > shell->historyFlag)
			                       ? shell->historyCount : shell->historyFlag);
		}
	}
	else if (dir == 1)
	{
		if (++shell->historyOffset > 0)
		{
			shell->historyOffset = 0;
			return;
		}
	}
	else
	{
		return;
	}
	shellClearLine(shell);
	if (shell->historyOffset == 0)
	{
		shell->cursor = shell->length = 0;
	}
	else
	{
		if ((shell->length = shellStringCopy(shell->buffer,
			shell->history[(shell->historyFlag + SHELL_HISTORY_MAX_NUMBER
			+ shell->historyOffset) % SHELL_HISTORY_MAX_NUMBER])) == 0)
		{
			return;
		}
		shell->cursor = shell->length;
		shellDisplay(shell, shell->buffer);
	}
}

static void shellEnter(SHELL_TypeDef *shell)
{
	unsigned char paramCount = 0;
	unsigned char quotes = 0;
	unsigned char record = 1;
	SHELL_CommandTypeDef *base;
	unsigned char runFlag = 0;
	int returnValue;

	if (shell->length == 0)
	{
		shellDisplay(shell, shell->command);
		return;
	}
    
	*(shell->buffer + shell->length++) = 0;

	shellHistoryAdd(shell);

	for (unsigned short i = 0; i < shell->length; i++)
	{
		if ((quotes != 0 ||
		    (*(shell->buffer + i) != ' ' &&
		    *(shell->buffer + i) != '\t' &&
		    *(shell->buffer + i) != ',')) &&
		    *(shell->buffer + i) != 0)
		{
			if (*(shell->buffer + i) == '\"')
			{
				quotes = quotes ? 0 : 1;
//				*(shell->buffer + i) = 0;
//				continue;
			}
			if (record == 1)
			{
				shell->param[paramCount++] = shell->buffer + i;
				record = 0;
			}
			if (*(shell->buffer + i) == '\\' &&
			    *(shell->buffer + i) != 0)
			{
				i++;
			}
		}
		else
		{
			*(shell->buffer + i) = 0;
			record = 1;
		}
	}
	shell->length = 0;
	shell->cursor = 0;
	if (paramCount == 0)
	{
		shellDisplay(shell, shell->command);
		return;
	}

	shellDisplay(shell, "\r\n");
	base = shell->commandBase;
	if (strcmp((const char *)shell->param[0], "help") == 0)
	{
		shell->isActive = 1;
		shellHelp(paramCount, shell->param);
		shell->isActive = 0;
		shellDisplay(shell, shell->command);
		return;
	}
	for (unsigned char i = 0; i < shell->commandNumber; i++)
	{
		if (strcmp((const char *)shell->param[0], (base + i)->name) == 0)
		{
			runFlag = 1;
			shell->isActive = 1;
			returnValue = shellExtRun((base + i)->function, paramCount, shell->param);
			shell->isActive = 0;
			shellDisplayReturn(shell, returnValue);
		}
	}
	if (runFlag == 0)
	{
		shellDisplay(shell, "Command not found\r\n");
	}
	shellDisplay(shell, shell->command);
}


static void shellBackspace(SHELL_TypeDef *shell)
{
	if (shell->length == 0)
	{
		return;
	}
	if (shell->cursor == shell->length)
	{
		shell->length--;
		shell->cursor--;
		shell->buffer[shell->length] = 0;
		shellDelete(shell, 1);
	}
	else if (shell->cursor > 0)
	{
		for (short i = 0; i < shell->length - shell->cursor; i++)
		{
			shell->buffer[shell->cursor + i - 1] = shell->buffer[shell->cursor + i];
		}
		shell->length--;
		shell->cursor--;
		shell->buffer[shell->length] = 0;
		shellDisplayByte(shell, '\b');
		for (short i = shell->cursor; i < shell->length; i++)
		{
			shellDisplayByte(shell, shell->buffer[i]);
		}
		shellDisplayByte(shell, ' ');
		for (short i = shell->length - shell->cursor + 1; i > 0; i--)
		{
			shellDisplayByte(shell, '\b');
		}
	}
}


static void shellTab(SHELL_TypeDef *shell)
{
	unsigned short maxMatch = SHELL_COMMAND_MAX_LENGTH;
	unsigned short lastMatchIndex = 0;
	unsigned short matchNum = 0;
	unsigned short length;
	SHELL_CommandTypeDef *base = shell->commandBase;

	if (shell->length != 0)
	{
		shell->buffer[shell->length] = 0;
		for (short i = 0; i < shell->commandNumber; i++)
		{
			if (shellStringCompare(shell->buffer, 
				(char *)(base + i)->name)
				== shell->length)
			{
				if (matchNum != 0)
				{
					if (matchNum == 1)
					{
						shellDisplay(shell, "\r\n");
					}
					shellDisplayItem(shell, lastMatchIndex);
					length = shellStringCompare((char *)(base + lastMatchIndex)->name,
						(char *)(base + i)->name);
					maxMatch = (maxMatch > length) ? length : maxMatch;
				}
				lastMatchIndex = i;
				matchNum++;
			}
		}

		if (matchNum == 0)
		{
			return;
		}
		if (matchNum == 1)
		{
			shellClearLine(shell);
		}
		if (matchNum != 0)
		{
			shell->length = shellStringCopy(shell->buffer,
				(char *)(base + lastMatchIndex)->name);
		}
		if (matchNum > 1)
		{
			shellDisplayItem(shell, lastMatchIndex);
			shellDisplay(shell, shell->command);
			shell->length = maxMatch;
		}
		shell->buffer[shell->length] = 0;
		shell->cursor = shell->length;
		shellDisplay(shell, shell->buffer);
	}
	else
	{
		shell->isActive = 1;
		shellHelp(1, (void *)0);
		shell->isActive = 0;
		shellDisplay(shell, shell->command);
	}

	static int time = 0;
    
	if (SHELL_GET_TICK())
	{
		if (matchNum == 1 && SHELL_GET_TICK() - time < SHELL_DOUBLE_CLICK_TIME)
		{
			shellClearLine(shell);
			for (short i = shell->length; i >= 0; i--)
			{
				shell->buffer[i + 5] = shell->buffer[i];
			}
			shellStringCopy(shell->buffer, "help");
			shell->buffer[4] = ' ';
			shell->length += 5;
			shell->cursor = shell->length;
			shellDisplay(shell, shell->buffer);
		}
		time = SHELL_GET_TICK();
	}
}


static void shellNormal(SHELL_TypeDef *shell, char data)
{
    
	if (shell->length < SHELL_COMMAND_MAX_LENGTH - 1)
	{
		if (shell->length == shell->cursor)
		{
			shell->buffer[shell->length++] = data;
			shell->cursor++;
			shellDisplayByte(shell, data);
		}
		else
		{
			for (short i = shell->length - shell->cursor; i > 0; i--)
			{
				shell->buffer[shell->cursor + i] = shell->buffer[shell->cursor + i - 1];
			}
			shell->buffer[shell->cursor++] = data;
			shell->buffer[++shell->length] = 0;
			for (short i = shell->cursor - 1; i < shell->length; i++)
			{
				shellDisplayByte(shell, shell->buffer[i]);
			}
			for (short i = shell->length - shell->cursor; i > 0; i--)
			{
				shellDisplayByte(shell, '\b');
			}
		}
	}
	else
	{
		shellDisplay(shell, "\r\nWarnig: Command is too long\r\n");
		shellDisplay(shell, shell->command);
		shellDisplay(shell, shell->buffer);
		shell->cursor = shell->length;
	}
}

void shellAnsi(SHELL_TypeDef *shell, char data)
{
	switch ((unsigned char)(shell->status))
	{
	case SHELL_ANSI_CSI:
		switch (data)
		{
		case 0x41://UP
			shellHistory(shell, 0);
			break;  
        
		case 0x42://DOWN
			shellHistory(shell, 1);
			break;

		case 0x43://RIGHT
			if (shell->cursor < shell->length)
			{
				shellDisplayByte(shell, shell->buffer[shell->cursor]);
				shell->cursor++;
			}
			break;

		case 0x44://LEFT
			if (shell->cursor > 0)
			{
				shellDisplayByte(shell, '\b');
				shell->cursor--;
			}
			break;

		default:
			break;
		}
		shell->status = SHELL_IN_NORMAL;
		break;

	case SHELL_ANSI_ESC:
		if (data == 0x5B)
		{
			shell->status = SHELL_ANSI_CSI;
		}
		else
		{
			shell->status = SHELL_IN_NORMAL;
		}
		break;

	default:
		break;
	}
}


void shellHandler(SHELL_TypeDef *shell, char data)
{
	if (shell->status == SHELL_IN_NORMAL)
	{
		switch (data)
		{
		case '\r':  
		case '\n':
			shellEnter(shell);
			break;

		case '\b':
		case 0x7F:
			shellBackspace(shell);
			break;

		case '\t':
			shellTab(shell);
			break;

		case 0x1B:
			shell->status = SHELL_ANSI_ESC;
			break;

		default:
			shellNormal(shell, data);
			break;
		}
	}
	else
	{
		shellAnsi(shell, data);
	}
    
}


/*
 *
 *this program should be hold in main while(1)
 *
 **/

void shellTask(SHELL_TypeDef *param, char data)
{
	SHELL_TypeDef *shell = param;
	if (shell->read == NULL)
	{
		shellDisplay(shell, "error: shell.read must be defined\r\n");
		while (1)
			;
	}
	if (shell->read(&data) == 0)
	{
		shellHandler(shell, data);
	}
}			  

static void shellDisplayItem(SHELL_TypeDef *shell, unsigned short index)
{
	unsigned short	spaceLength;
	SHELL_CommandTypeDef *base = shell->commandBase;
	spaceLength = 22 - shellDisplay(shell, (base + index)->name);
	spaceLength = (spaceLength > 0) ? spaceLength : 4;
	do 
	{
		shellDisplay(shell, " ");
	} 
	while (--spaceLength);
	
	shellDisplay(shell, "--");
	shellDisplay(shell, (base + index)->desc);
	shellDisplay(shell, "\r\n");
}

void Shell_Out_Char(const char outdata)
{
	printch(outdata);
}

signed char Shell_In_Char(char *indata)
{
	if (uartDataFlag)
	{
		return 0;
	}
	else
	{
		*indata = '\0';
		return -1;
	}	
}


/*********************************** System command ***************************************************/

void shellHelp(int argc, char *argv[])
{
	SHELL_TypeDef *shell = shellGetCurrent();
	if (!shell)
	{
		return;
	}

	if (argc == 1)
	{
		shellDisplay(shell, "\r\nCOMMAND LIST:\r\n\r\n");       
		for (unsigned short i = 0; i < shell->commandNumber; i++)
		{
			shellDisplayItem(shell, i);
		}
	}
	else if (argc == 2) {
		SHELL_CommandTypeDef *base = shell->commandBase;
		for (unsigned char i = 0; i < shell->commandNumber; i++)
		{
			if (strcmp((const char *)argv[1], (base + i)->name) == 0)
			{
                
				shellDisplay(shell, "command help --");
				shellDisplay(shell, (base + i)->name);
				shellDisplay(shell, ":\r\n");
				shellDisplay(shell, (base + i)->desc);
				shellDisplay(shell, "\r\n");
				if ((base + i)->help)
				{
					shellDisplay(shell, (base + i)->help);
					shellDisplay(shell, "\r\n");
				}
				return;
			}
		}
		shellDisplay(shell, "command not found\r\n");
	}
}

SHELL_EXPORT_CMD_EX(help, shellHelp, command help, help[command]--show help info of command);

void shellClear(void)
{
	SHELL_TypeDef *shell = shellGetCurrent();
	if (!shell)
	{
		return;
	}
	shellDisplay(shell, "\033[2J\033[1H");
}

SHELL_EXPORT_CMD(cls, shellClear, clear command line);


/*********************************** user command ***************************************************/

//SHELL_EXPORT_CMD(keyscan, Key_Test, KEY INT TEST);

void Get_ChipMessage(void)
{
	int flashSize = *(volatile uint16_t *)(0x1FFF7A22);
	int chipID1 = *(volatile uint32_t *)(0x1FFF7A10);
	int chipID2 = *(volatile uint32_t *)(0x1FFF7A14);
	int chipID3 = *(volatile uint32_t *)(0x1FFF7A18);
	printf("Chip ID is: %x-%x-%x\r\n", chipID1, chipID2, chipID3);
	printf("Flash size is: %d KB\r\n", flashSize);
}

void STM32_ID(void)
{
	SHELL_TypeDef *shell = shellGetCurrent();
	if (!shell)
	{
		return ;
	}
	else
	{
		Get_ChipMessage();
	}
	
}
